Two-Dimensional Moment Frame Analysis

This is a complete example of frame analysis. Although it is a 2D case, it can be applied to more complex 3D models. While it demonstrates linear elastic analysis, it is also applicable to nonlinear elastoplastic analysis.

[1]:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
import openseespy.opensees as ops

import opstool as opst
import opstool.vis.pyvista as opsvis

Create the model

image1

Create the OpenSees model

[2]:
def FEModel():
    ops.wipe()
    ops.model("basic", "-ndm", 2, "-ndf", 3)

    # %% Defining Nodes

    ops.node(1, 0.000000e00, 0.000000e00)
    ops.node(2, 3.600000e02, 0.000000e00)
    ops.node(3, 7.200000e02, 0.000000e00)
    ops.node(4, 0.000000e00, 1.620000e02)
    ops.node(5, 3.600000e02, 1.620000e02)
    ops.node(6, 7.200000e02, 1.620000e02)
    ops.node(7, 0.000000e00, 3.240000e02)
    ops.node(8, 3.600000e02, 3.240000e02)
    ops.node(9, 7.200000e02, 3.240000e02)
    ops.node(10, 0.000000e00, 4.800000e02)
    ops.node(11, 3.600000e02, 4.800000e02)
    ops.node(12, 7.200000e02, 4.800000e02)
    ops.node(13, 0.000000e00, 6.360000e02)
    ops.node(14, 3.600000e02, 6.360000e02)
    ops.node(15, 7.200000e02, 6.360000e02)
    ops.node(16, 0.000000e00, 7.920000e02)
    ops.node(17, 3.600000e02, 7.920000e02)
    ops.node(18, 7.200000e02, 7.920000e02)
    ops.node(19, 0.000000e00, 9.480000e02)
    ops.node(20, 3.600000e02, 9.480000e02)
    ops.node(21, 7.200000e02, 9.480000e02)
    ops.node(22, 0.000000e00, 1.104000e03)
    ops.node(23, 3.600000e02, 1.104000e03)
    ops.node(24, 7.200000e02, 1.104000e03)

    # %% write node restraint

    ops.fix(1, 1, 1, 1)
    ops.fix(2, 1, 1, 1)
    ops.fix(3, 1, 1, 1)

    # %% Define the rigidDiaphragm
    ops.rigidDiaphragm(1, 5, 4, 6)
    ops.rigidDiaphragm(1, 8, 7, 9)
    ops.rigidDiaphragm(1, 11, 10, 12)
    ops.rigidDiaphragm(1, 14, 13, 15)
    ops.rigidDiaphragm(1, 17, 16, 18)
    ops.rigidDiaphragm(1, 20, 19, 21)
    ops.rigidDiaphragm(1, 23, 22, 24)

    # %% Defining Frame Elements

    ops.geomTransf("Linear", 1)
    ops.element("elasticBeamColumn", 1, 1, 4, 7.230000e01, 2.950000e04, 3.230000e03, 1)
    ops.element("elasticBeamColumn", 2, 4, 7, 7.230000e01, 2.950000e04, 3.230000e03, 1)
    ops.element("elasticBeamColumn", 3, 7, 10, 7.230000e01, 2.950000e04, 3.230000e03, 1)
    ops.element("elasticBeamColumn", 4, 10, 13, 6.210000e01, 2.950000e04, 2.670000e03, 1)
    ops.element("elasticBeamColumn", 5, 13, 16, 6.210000e01, 2.950000e04, 2.670000e03, 1)
    ops.element("elasticBeamColumn", 6, 16, 19, 5.170000e01, 2.950000e04, 2.150000e03, 1)
    ops.element("elasticBeamColumn", 7, 19, 22, 5.170000e01, 2.950000e04, 2.150000e03, 1)
    ops.element("elasticBeamColumn", 8, 2, 5, 8.440000e01, 2.950000e04, 3.910000e03, 1)
    ops.element("elasticBeamColumn", 9, 5, 8, 8.440000e01, 2.950000e04, 3.910000e03, 1)
    ops.element("elasticBeamColumn", 10, 8, 11, 8.440000e01, 2.950000e04, 3.910000e03, 1)
    ops.element("elasticBeamColumn", 11, 11, 14, 7.230000e01, 2.950000e04, 3.230000e03, 1)
    ops.element("elasticBeamColumn", 12, 14, 17, 7.230000e01, 2.950000e04, 3.230000e03, 1)
    ops.element("elasticBeamColumn", 13, 17, 20, 6.210000e01, 2.950000e04, 2.670000e03, 1)
    ops.element("elasticBeamColumn", 14, 20, 23, 6.210000e01, 2.950000e04, 2.670000e03, 1)
    ops.element("elasticBeamColumn", 15, 3, 6, 7.230000e01, 2.950000e04, 3.230000e03, 1)
    ops.element("elasticBeamColumn", 16, 6, 9, 7.230000e01, 2.950000e04, 3.230000e03, 1)
    ops.element("elasticBeamColumn", 17, 9, 12, 7.230000e01, 2.950000e04, 3.230000e03, 1)
    ops.element("elasticBeamColumn", 18, 12, 15, 6.210000e01, 2.950000e04, 2.670000e03, 1)
    ops.element("elasticBeamColumn", 19, 15, 18, 6.210000e01, 2.950000e04, 2.670000e03, 1)
    ops.element("elasticBeamColumn", 20, 18, 21, 5.170000e01, 2.950000e04, 2.150000e03, 1)
    ops.element("elasticBeamColumn", 21, 21, 24, 5.170000e01, 2.950000e04, 2.150000e03, 1)
    ops.element("elasticBeamColumn", 22, 4, 5, 4.710000e01, 2.950000e04, 5.120000e03, 1)
    ops.element("elasticBeamColumn", 23, 7, 8, 4.710000e01, 2.950000e04, 5.120000e03, 1)
    ops.element("elasticBeamColumn", 24, 10, 11, 3.830000e01, 2.950000e04, 4.020000e03, 1)
    ops.element("elasticBeamColumn", 25, 13, 14, 3.830000e01, 2.950000e04, 4.020000e03, 1)
    ops.element("elasticBeamColumn", 26, 16, 17, 3.250000e01, 2.950000e04, 3.330000e03, 1)
    ops.element("elasticBeamColumn", 27, 19, 20, 3.250000e01, 2.950000e04, 3.330000e03, 1)
    ops.element("elasticBeamColumn", 28, 22, 23, 3.250000e01, 2.950000e04, 3.330000e03, 1)
    ops.element("elasticBeamColumn", 29, 5, 6, 4.710000e01, 2.950000e04, 5.120000e03, 1)
    ops.element("elasticBeamColumn", 30, 8, 9, 4.710000e01, 2.950000e04, 5.120000e03, 1)
    ops.element("elasticBeamColumn", 31, 11, 12, 3.830000e01, 2.950000e04, 4.020000e03, 1)
    ops.element("elasticBeamColumn", 32, 14, 15, 3.830000e01, 2.950000e04, 4.020000e03, 1)
    ops.element("elasticBeamColumn", 33, 17, 18, 3.250000e01, 2.950000e04, 3.330000e03, 1)
    ops.element("elasticBeamColumn", 34, 20, 21, 3.250000e01, 2.950000e04, 3.330000e03, 1)
    ops.element("elasticBeamColumn", 35, 23, 24, 3.250000e01, 2.950000e04, 3.330000e03, 1)

    # %% Define the mass
    ops.mass(5, 0.49, 0.0, 0.0)
    ops.mass(8, 0.49, 0.0, 0.0)
    ops.mass(11, 0.49, 0.0, 0.0)
    ops.mass(14, 0.49, 0.0, 0.0)
    ops.mass(17, 0.49, 0.0, 0.0)
    ops.mass(20, 0.49, 0.0, 0.0)
    ops.mass(23, 0.49, 0.0, 0.0)

Let’s first visualize the geometry by Pyvista:

[3]:
FEModel()
[4]:
opsvis.set_plot_props(line_width=4, point_size=2)
plotter = opsvis.plot_model(show_ele_numbering=True, show_node_numbering=True)
# plotter.show()  # For interactive plot

images/model_pyvista.png

You can also use the following code to visualize the model by using Plotly:

[6]:
opst.vis.plotly.set_plot_props(line_width=4, point_size=2)
fig = opst.vis.plotly.plot_model()
fig.write_html("model.html")
fig.show()

Data type cannot be displayed: application/vnd.plotly.v1+json

Eigenvalue analysis

solver=”-fullGenLapack” is intended to extract all seventh-order modes. This solver should be avoided in actual large models.

[7]:
opst.post.save_eigen_data(odb_tag="eigen", mode_tag=6, solver="-fullGenLapack")
WARNING - the 'fullGenLapack' eigen solver is VERY SLOW. Consider using the default eigen solver.Using DomainModalProperties - Developed by: Massimo Petracca, Guido Camata, ASDEA Software Technology
OPSTOOL ::  Eigen data has been saved to .opstool.output/EigenData-eigen.nc!

odb_tag = “eigen” is used to indentify the output database for eigenvalue analysis.

[8]:
opsvis.set_plot_props(cmap="jet", line_width=4, point_size=5, font_size=12)
plotter = opsvis.plot_eigen(mode_tags=[1, 6], odb_tag="eigen", subplots=True, bc_scale=3)
# plotter.show() # for auto
OPSTOOL ::  Loading eigen data from .opstool.output/EigenData-eigen.nc ...
[9]:
plotter.off_screen = True  # for screenshot, you should set this to True for interactive use
_ = plotter.screenshot("images/eigen_pyvista.png")
plotter.close()

images/eigen_pyvista.png

We can also retrieve the data of eigenvalue analysis:

[10]:
model_props, eigen_vectors = opst.post.get_eigen_data(odb_tag="eigen")
model_props_df = model_props.to_pandas()  # to pandas DataFrame
print(model_props_df.columns)
OPSTOOL ::  Loading eigen data from .opstool.output/EigenData-eigen.nc ...
Index(['eigenLambda', 'eigenOmega', 'eigenFrequency', 'eigenPeriod',
       'partiFactorMX', 'partiFactorMY', 'partiFactorRMZ', 'partiMassMX',
       'partiMassMY', 'partiMassRMZ', 'partiMassesCumuMX', 'partiMassesCumuMY',
       'partiMassesCumuRMZ', 'partiMassRatiosMX', 'partiMassRatiosMY',
       'partiMassRatiosRMZ', 'partiMassRatiosCumuMX', 'partiMassRatiosCumuMY',
       'partiMassRatiosCumuRMZ'],
      dtype='object', name='Properties')
[11]:
print("Modal period: ", model_props_df["eigenPeriod"])
print("-" * 50)
print("Participation mass ratio: ", model_props_df["partiMassRatiosMX"])
print("-" * 50)
print("Cumulative participation mass ratio: ", model_props_df["partiMassRatiosCumuMX"])
Modal period:  modeTags
1    1.273211
2    0.431278
3    0.242043
4    0.160179
5    0.118990
6    0.095064
Name: eigenPeriod, dtype: float64
--------------------------------------------------
Participation mass ratio:  modeTags
1    79.962669
2    11.336182
3     4.180994
4     2.115029
5     1.414555
6     0.679967
Name: partiMassRatiosMX, dtype: float64
--------------------------------------------------
Cumulative participation mass ratio:  modeTags
1    79.962669
2    91.298850
3    95.479845
4    97.594874
5    99.009429
6    99.689396
Name: partiMassRatiosCumuMX, dtype: float64

Static analysis

Defining Lateral Distributed Loads:

[12]:
FEModel()
# %% Define the load pattern
ops.timeSeries("Linear", 1)
ops.pattern("Plain", 1, 1)
ops.load(4, 2.5, 0.0, 0.0)
ops.load(7, 5.0, 0.0, 0.0)
ops.load(10, 7.5, 0.0, 0.0)
ops.load(13, 10.0, 0.0, 0.0)
ops.load(16, 12.5, 0.0, 0.0)
ops.load(19, 15.0, 0.0, 0.0)
ops.load(22, 20.0, 0.0, 0.0)

Re-examine the model:

[13]:
opsvis.set_plot_props(line_width=4, point_size=4, font_size=12)
fig = opsvis.plot_model(show_nodal_loads=True, load_scale=2, bc_scale=3)
# fig.show()  # fig.show() for practical use

images/model_pyvista2.png

To perform the analysis:

[15]:
n_steps = 10
ops.system("BandGeneral")
ops.constraints("Transformation")
ops.numberer("RCM")
ops.test("NormDispIncr", 1.0e-12, 10, 3)
ops.algorithm("Linear")
ops.integrator("LoadControl", 1 / n_steps)
ops.analysis("Static")

Save data:

[16]:
odb = opst.post.CreateODB(odb_tag="static")
for _ in range(n_steps):
    ops.analyze(1)  # one step of analysis
    odb.fetch_response_step()  # fetch the response on the current step
odb.save_response()
OPSTOOL ::  All responses data with _odb_tag = static saved in .opstool.output/RespStepData-static.nc!

Retrieve the nodal displacements:

[17]:
node_resp = opst.post.get_nodal_responses(odb_tag="static")
print(
    "Node 22 displacement in x direction: ",
    node_resp["disp"].sel(nodeTags=22, DOFs="UX").data[-1],
)
OPSTOOL ::  Loading all response data from .opstool.output/RespStepData-static.nc ...
Node 22 displacement in x direction:  1.450757

Retrieve element response:

[18]:
ele_resp = opst.post.get_element_responses(odb_tag="static", ele_type="Frame")
print(ele_resp.data_vars)
OPSTOOL ::  Loading Frame response data from .opstool.output/RespStepData-static.nc ...
Data variables:
    localForces          (time, eleTags, localDofs) float32 18kB -0.0 ... -527.7
    basicForces          (time, eleTags, basicDofs) float32 9kB 0.0 -0.0 ... 0.0
    basicDeformations    (time, eleTags, basicDofs) float32 9kB 0.0 -0.0 ... 0.0
    plasticDeformation   (time, eleTags, basicDofs) float32 9kB 0.0 -0.0 ... 0.0
    sectionForces        (time, eleTags, secPoints, secDofs) float32 65kB -0....
    sectionDeformations  (time, eleTags, secPoints, secDofs) float32 65kB 0.0...
    sectionLocs          (time, eleTags, secPoints, locs) float32 32kB 0.0 .....
[19]:
ele_forces = ele_resp["sectionForces"]
print("M:", ele_forces.sel(eleTags=1, secDofs="MZ", secPoints=1).data)
print("V:", ele_forces.sel(eleTags=1, secDofs="VY", secPoints=1).data)
print("N:", ele_forces.sel(eleTags=1, secDofs="N", secPoints=1).data)
M: [  -0.       232.46773  464.93546  697.4032   929.8709  1162.3386
 1394.8064  1627.274   1859.7418  2092.2095  2324.6772 ]
V: [ -0.         -2.067208   -4.134416   -6.2016244  -8.268832  -10.3360405
 -12.403249  -14.470457  -16.537664  -18.604874  -20.672081 ]
N: [-0.         6.9986734 13.997347  20.99602   27.994694  34.993366
 41.99204   48.990715  55.989388  62.98806   69.98673  ]

Visualize node responses:

[20]:
on_notebook = True  # In practical use, you can set it to False
jupyter_backend = "static"  # In practical use, you can set it to None
[21]:
opsvis.set_plot_props(cmap="coolwarm", notebook=on_notebook, line_width=4, title_font_size=10)
plotter = opsvis.plot_nodal_responses(
    odb_tag="static", resp_type="disp", resp_dof=["UX", "UY"], slides=True, defo_scale=2.0
)
plotter.show(jupyter_backend=jupyter_backend)
OPSTOOL ::  Loading response data from .opstool.output/RespStepData-static.nc ...
../../../_images/examples_post_Frame2D_test_model_43_1.png

Visualizing Element Response:

[22]:
# Moment response
opsvis.set_plot_props(cmap="seismic", line_width=1, title_font_size=14)

opsvis.plot_frame_responses(
    odb_tag="static",
    resp_type="sectionForces",
    resp_dof="Mz",
    unit_symbol="N·mm",
    unit_factor=1.0,
    slides=True,
    scale=-2.0,
    style="wireframe",
).show(jupyter_backend=jupyter_backend)
OPSTOOL ::  Loading response data from .opstool.output/RespStepData-static.nc ...
../../../_images/examples_post_Frame2D_test_model_45_1.png
[23]:
# Axial response
opsvis.plot_frame_responses(
    odb_tag="static",
    resp_type="sectionForces",
    resp_dof="N",
    slides=True,
    scale=-2.0,
    line_width=3,
    style="wireframe",
).show(jupyter_backend=jupyter_backend)
OPSTOOL ::  Loading response data from .opstool.output/RespStepData-static.nc ...
../../../_images/examples_post_Frame2D_test_model_46_1.png

Seismic response analysis

Load the ground motion data

All files can be downloaded here: click

[24]:
gmdata = np.loadtxt("ELCENTRO.txt")
time = gmdata[:, 0]
accel = gmdata[:, 1]


print(len(time))
plt.plot(time, accel)
plt.show()
1560
../../../_images/examples_post_Frame2D_test_model_50_1.png

Create the ground motion load pattern

[25]:
FEModel()
ops.timeSeries("Path", 2, "-time", *time, "-values", *accel, "-factor", 386.4)
ops.pattern("UniformExcitation", 2, 1, "-accel", 2)

Create the Rayleigh damping

[26]:
xDamp = 0.05
MpropSwitch = 1.0
KcurrSwitch = 0.0
KcommSwitch = 1.0
KinitSwitch = 0.0
nEigenI = 1  # mode 1
nEigenJ = 2  # mode 2
lambdaN = ops.eigen(nEigenJ)  # eigenvalue analysis for nEigenJ modes
lambdaI = lambdaN[nEigenI - 1]  # eigenvalue mode i
lambdaJ = lambdaN[nEigenJ - 1]  # eigenvalue mode j
omegaI = np.sqrt(lambdaI)
omegaJ = np.sqrt(lambdaJ)
# M-prop. damping; D = alphaM*M
alphaM = MpropSwitch * xDamp * (2 * omegaI * omegaJ) / (omegaI + omegaJ)
# current-K;      +beatKcurr*KCurrent
betaKcurr = KcurrSwitch * 2.0 * xDamp / (omegaI + omegaJ)
# last-committed K;   +betaKcomm*KlastCommitt
betaKcomm = KcommSwitch * 2.0 * xDamp / (omegaI + omegaJ)
betaKinit = KinitSwitch * 2.0 * xDamp / (omegaI + omegaJ)
ops.rayleigh(alphaM, 0.0, 0.0, betaKcomm)

Perform analysis and save data

[27]:
ops.wipeAnalysis()
ops.system("BandGeneral")
ops.constraints("Transformation")
ops.numberer("RCM")
ops.test("NormDispIncr", 1.0e-12, 10, 3)
ops.algorithm("Linear")
ops.integrator("HHT", 1.0, 0.5, 0.25)
ops.analysis("Transient")
[28]:
n_steps, dt = 1600, 0.02
odb = opst.post.CreateODB(odb_tag="seismic")
for _ in range(n_steps):
    ops.analyze(1, dt)
    odb.fetch_response_step()
odb.save_response(zlib=True)
# zlib=True to compress the data
OPSTOOL ::  All responses data with _odb_tag = seismic saved in .opstool.output/RespStepData-seismic.nc!

Retrieve Node Responses

[29]:
node_resp = opst.post.get_nodal_responses(odb_tag="seismic")
node_disp22 = node_resp["disp"].sel(nodeTags=22, DOFs="UX")
node_vel22 = node_resp["vel"].sel(nodeTags=22, DOFs="UX")
node_accel22 = node_resp["accel"].sel(nodeTags=22, DOFs="UX")
OPSTOOL ::  Loading all response data from .opstool.output/RespStepData-seismic.nc ...

Draw a time series plot:

[30]:
fig, axes = plt.subplots(3, 1, sharex=True)
time = node_disp22.time

# Define colors and line styles
colors = ["#136ad5", "#fb8a2e"]  # Use neutral colors for publication standards
line_styles = ["-", "--"]  # Solid and dashed lines for differentiation

# Plot data with clear labels
axes[0].plot(time, node_disp22.data, color=colors[0], linestyle=line_styles[0], linewidth=1.5)

axes[1].plot(time, node_vel22.data, color=colors[0], linestyle=line_styles[0], linewidth=1.5)

axes[2].plot(time, node_accel22.data, color=colors[0], linestyle=line_styles[0], linewidth=1.5)

# Set axis labels and title font sizes
axes[0].set_ylabel("u", fontsize=10)
axes[1].set_ylabel("v", fontsize=10)
axes[2].set_ylabel("a", fontsize=10)
axes[2].set_xlabel("Time (s)", fontsize=10)

# Customize each subplot
for ax in axes:
    ax.tick_params(axis="both", which="major", labelsize=8)  # Set tick font size
    ax.grid(True, linestyle="--", linewidth=0.5, alpha=0.7)  # Add light grid lines
    # ax.legend(fontsize=9, loc="best", frameon=False)  # Simple legend without box

# Format X-axis ticks to have consistent significant figures
for ax in axes:
    ax.xaxis.set_major_formatter(ticker.FormatStrFormatter("%.1f"))

# Adjust spacing between subplots
fig.subplots_adjust(hspace=0.2)  # Adjust vertical spacing
# Save figure in a publication-friendly format
# plt.savefig("fig-node22resp.pdf", bbox_inches="tight")
plt.show()
../../../_images/examples_post_Frame2D_test_model_61_0.png
[31]:
print("OpenSees Node 22 Disp Max:", node_disp22.data.max())
print("OpenSees Node 22 Vel Max:", node_vel22.data.max())
print("OpenSees Node 22 Accel Max:", node_accel22.data.max())
print("-" * 50)
print("OpenSees Node 22 Disp Min:", node_disp22.data.min())
print("OpenSees Node 22 Vel Min:", node_vel22.data.min())
print("OpenSees Node 22 Accel Min:", node_accel22.data.min())
OpenSees Node 22 Disp Max: 4.886218
OpenSees Node 22 Vel Max: 24.562452
OpenSees Node 22 Accel Max: 284.1298
--------------------------------------------------
OpenSees Node 22 Disp Min: -4.141897
OpenSees Node 22 Vel Min: -25.271881
OpenSees Node 22 Accel Min: -249.5041

Retrieve Element Responses

[32]:
ele_resp = opst.post.get_element_responses(odb_tag="seismic", ele_type="Frame", resp_type="sectionForces")
frame1Mz = -ele_resp.sel(eleTags=1, secDofs="MZ", secPoints=1)
frame1N = ele_resp.sel(eleTags=1, secDofs="N", secPoints=1)
frame1Vy = ele_resp.sel(eleTags=1, secDofs="VY", secPoints=1)
OPSTOOL ::  Loading Frame sectionForces response data from .opstool.output/RespStepData-seismic.nc ...
[33]:
fig, axes = plt.subplots(3, 1, sharex=True)
time = frame1Mz.time

# Define colors and line styles
colors = ["#136ad5", "#fb8a2e"]  # Use neutral colors for publication standards
line_styles = ["-", "--"]  # Solid and dashed lines for differentiation

# Plot data with clear labels
axes[0].plot(time, frame1N.data, color=colors[0], linestyle=line_styles[0], linewidth=1.5)

axes[1].plot(time, frame1Vy.data, color=colors[0], linestyle=line_styles[0], linewidth=1.5)

axes[2].plot(time, frame1Mz.data, color=colors[0], linestyle=line_styles[0], linewidth=1.5)

# Set axis labels and title font sizes
axes[0].set_ylabel("N", fontsize=10)
axes[1].set_ylabel("V", fontsize=10)
axes[2].set_ylabel("M", fontsize=10)
axes[2].set_xlabel("Time (s)", fontsize=10)

# Customize each subplot
for ax in axes:
    ax.tick_params(axis="both", which="major", labelsize=8)  # Set tick font size
    ax.grid(True, linestyle="--", linewidth=0.5, alpha=0.7)  # Add light grid lines
    # ax.legend(fontsize=9, loc="best", frameon=False)  # Simple legend without box

# Format X-axis ticks to have consistent significant figures
for ax in axes:
    ax.xaxis.set_major_formatter(ticker.FormatStrFormatter("%.1f"))

# Adjust spacing between subplots
fig.subplots_adjust(hspace=0.2)  # Adjust vertical spacing
# Save figure in a publication-friendly format
# plt.savefig("fig-frame1-forces.pdf", bbox_inches="tight")
plt.show()
../../../_images/examples_post_Frame2D_test_model_65_0.png
[34]:
print("OpenSees Frame 1 N Max:", frame1N.data.max())
print("OpenSees Frame 1 Vy Max:", frame1Vy.data.max())
print("OpenSees Frame 1 Mz Max:", frame1Mz.data.max())
print("-" * 50)
print("OpenSees Frame 1 N Min:", frame1N.data.min())
print("OpenSees Frame 1 Vy Min:", frame1Vy.data.min())
print("OpenSees Frame 1 Mz Min:", frame1Mz.data.min())
OpenSees Frame 1 N Max: 233.99113
OpenSees Frame 1 Vy Max: 61.886253
OpenSees Frame 1 Mz Max: 6995.9204
--------------------------------------------------
OpenSees Frame 1 N Min: -196.78415
OpenSees Frame 1 Vy Min: -71.202286
OpenSees Frame 1 Mz Min: -8003.0625

Creating an .MP4 animation

[35]:
opsvis.set_plot_props(font_size=8, title_font_size=10, line_width=5, point_size=3, cmap=["blue"])
plotter = opsvis.plot_nodal_responses_animation(
    odb_tag="seismic",
    resp_type="disp",
    resp_dof=["UX", "UY"],
    framerate=100,  # Frames per second
    savefig="NodalRespAnimation.mp4",
    defo_scale=10.0,
)
plotter.close()  # Dont forget to close the plotter after saving the animation
OPSTOOL ::  Loading response data from .opstool.output/RespStepData-seismic.nc ...
Animation has been saved to NodalRespAnimation.mp4!
[36]:
from IPython.display import Video

Video("NodalRespAnimation.mp4", embed=True, width=640, height=360)
[36]: